import {
  computed,
  onUnmounted,
  ref,
  Ref,
  Component,
  defineComponent,
  h,
  inject,
  provide,
} from "vue";
import {
  DRAWER_DATA,
  CommonDrawerType,
  DRAWER_DATA_CHANGE,
  DRAWER_HOOK_ONSHOW,
} from "./constant";
import { drawerFactory } from "./drawer-factory";

interface DrawerEvent {
  eventKey: string;
  params?: object;
}

interface DrawerData {
  drawerName: string;
  drawerTitle: string;
  drawerComponent: Component;
}

interface DrawerItem {
  component: Component;
  drawerTitle: string;
  events: DrawerEvent[];
  hooks: {
    [key: string]: Function[];
  };
}

export class DrawerManager {
  drawerMap: Partial<Record<CommonDrawerType, DrawerData>> = {};
  drawerStack: Ref<DrawerItem[]> = ref([]);
  drawerUid = 0;

  // 抽屉容器
  containerHooks: Record<string, Function[]> = {
    [DRAWER_HOOK_ONSHOW]: [],
  };

  // cached
  cachedKeys: Ref<string[]> = ref([]);

  stackTop = computed(() => {
    if (!this.drawerStack.value.length) {
      return false;
    }
    return this.drawerStack.value[this.drawerStack.value.length - 1];
  });

  constructor() {}

  // 将抽屉注册到 drawerMap
  register<T>(
    drawerType: CommonDrawerType,
    drawerComponent: Component,
    drawerName: string,
    drawerTitle: string
  ) {
    this.drawerMap[drawerType] = {
      drawerName,
      drawerTitle,
      drawerComponent,
    };

    const drawer = drawerFactory<T>(drawerType, this);
    return drawer;
  }
  // 抽屉入栈
  pushStack(drawerType: CommonDrawerType, props: any) {
    const drawerData = this.drawerMap[drawerType]!;
    const componentName = `${drawerData.drawerName}&${++this.drawerUid}`;

    const currentDrawerData: DrawerItem = {
      component: drawerData.drawerComponent,
      drawerTitle: drawerData.drawerTitle,
      events: [],
      hooks: {
        [DRAWER_HOOK_ONSHOW]: [],
      },
    };

    const realComponent = defineComponent({
      name: componentName,
      setup() {
        provide(DRAWER_DATA, currentDrawerData);
      },
      render() {
        return h(drawerData.drawerComponent, props);
      },
    });

    currentDrawerData.component = realComponent;

    this.cachedKeys.value = this.cachedKeys.value.filter((item) => {
      const drawerName = item.split("&")[0];
      return drawerName !== drawerData.drawerName;
    });

    this.cachedKeys.value.push(componentName);

    this.drawerStack.value.push(currentDrawerData);
  }
  // 抽屉出栈
  // preserveNumber 需要保留的栈元素数量
  popStack(preserveNumber?: number) {
    if (!this.stackTop.value) {
      return;
    }

    if (!preserveNumber) {
      preserveNumber = this.drawerStack.value.length - 1;
    }
    const popDrawers = this.drawerStack.value.slice(preserveNumber);
    this.drawerStack.value = this.drawerStack.value.slice(0, preserveNumber);
    const popEvents = popDrawers.reduce(
      (prev, cur) => [...prev, ...cur.events],
      [] as DrawerEvent[]
    );

    const hasChange = popEvents
      .map((item) => item.eventKey)
      .includes(DRAWER_DATA_CHANGE);

    if (!this.stackTop.value) {
      this.containerHooks[DRAWER_HOOK_ONSHOW].forEach((cb) =>
        cb(hasChange, popEvents)
      );
    } else {
      // 根据之前上层的事件触发当前层 onShow
      this.stackTop.value.hooks[DRAWER_HOOK_ONSHOW].forEach((cb) =>
        cb(hasChange, popEvents)
      );
      // 事件传递 将上层的事件合并到当前层中
      this.stackTop.value.events = [
        ...this.stackTop.value.events,
        ...popEvents,
      ];
    }
  }
  // 清空栈
  clearStack() {
    this.popStack(0);
  }
  // 注册抽屉hook
  registerHook(hook: string, cb: Function) {
    if (!this.stackTop.value) {
      this.containerHooks[hook].push(cb);
    } else {
      this.stackTop.value.hooks[hook].push(cb);
    }
  }
  // 移除抽屉hook
  removeHook(hook: string, cb: Function) {
    if (!this.stackTop.value) {
      this.containerHooks[hook] = this.containerHooks[hook].filter(
        (item) => item !== cb
      );
    } else {
      this.stackTop.value.hooks[hook] = this.stackTop.value.hooks[hook].filter(
        (item) => item !== cb
      );
    }
  }
}

export const useInjectDrawerData = () => {
  return inject<DrawerItem>(DRAWER_DATA);
};

export const drawerManager = new DrawerManager();

export const onShow = (
  cb: (hasChange: boolean, events: DrawerEvent[]) => void
) => {
  drawerManager.registerHook(DRAWER_HOOK_ONSHOW, cb);
  const clear = () => drawerManager.removeHook(DRAWER_HOOK_ONSHOW, cb);
  onUnmounted(clear);
  return clear;
};

export const usePushDrawerEvent = () => {
  const drawerData = useInjectDrawerData();
  const pushDrawerEvent = (
    eventKey: string = DRAWER_DATA_CHANGE,
    params?: any
  ) => {
    if (!drawerData) {
      return;
    }
    drawerData.events.push({
      eventKey,
      params,
    });
  };
  return {
    pushDrawerEvent,
  };
};

export function useDrawerFactory(drawer: any) {
  return drawer;
}

export const useDrawerManager = () => {
  return {
    drawerManager,
  };
};
